package game.maze;


import game.Game;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


import shared.Tiles;
import shared.io.game.Lightable;
import shared.io.game.Player;
import shared.io.game.Room;

public class Maze {
   /**
    * Width of the UNIQUE part of the maze. Not to be mistaken for map width.
    * Must be an odd
    */
   private static final int WIDTH = 19;
   /**
    * Height of the UNIQUE part of the maze. Not to be mistaken for map height.
    * Must be an odd
    */
   private static final int HEIGHT = 13;
   /**
    * List of rooms that have light in them (light > 0)
    */
   private List<Room> lightRooms;
   /**
    * Map of rooms
    */
   private Room[][] map;
   /**
    * Possible spawnpoints for players
    */
   private List<Point> spawns;
   private Game game;
   private enum Direction {NORTH, EAST, SOUTH, WEST};
   private Point exit;
   private static final int EXIT_TORCH_DISTANCE = 2;
   
   public Maze(int players, int torchDistance, Game game) {
      this.game = game;
      lightRooms = new ArrayList<Room>();
      MazeGenerator mazeGen = new MazeGenerator(WIDTH, HEIGHT);
      byte[][] byteMap = mazeGen.getRandomMaze(players);
      map = new Room[mazeGen.getWidth()][mazeGen.getHeight()];
      for (int x = 0; x < mazeGen.getWidth(); x++) {
         for (int y = 0; y < mazeGen.getHeight(); y++) {
            map[x][y] = new Room(x, y, byteMap[x][y], 0);
            if (map[x][y].getTile() == Tiles.EXIT) {
               exit = new Point(x, y);
            }
         }
      }
      
      //mazeGen.printMaze();
      
      spawns = new ArrayList<Point>();
      spawns.add(new Point(1, 1));
      spawns.add(new Point(1, getHeight() - 2));
      spawns.add(new Point(getWidth() - 2, 1));
      spawns.add(new Point(getWidth() - 2, getHeight() - 2));

   }
   
   /**
    * Width of the map
    */
   public int getWidth() {
      return map.length;
   }
   
   /**
    * Height of the map
    */
   public int getHeight() {
      return map[0].length;
   }
   
   /**
    * Gets a room on the map. If out of bounds returns null.
    */
   public Room getRoom(int x, int y) {
      try {
         return map[x][y];
      } catch (IndexOutOfBoundsException e) {
         return null;
      }
   }
  
   /**
    * Gets a list of currently visible rooms based on the list of players.
    */
   public List<Room> getVisibleRooms(List<Player> players, int torchDistance) {
      for (Room room : lightRooms)room.setLight(0);
      lightRooms.clear();
      
      for (Player player : players) {
         getVisibleRooms(player, torchDistance);
      }
      
      return lightRooms;
   }
   
   /**
    * Sets the player torch travel way.
    */
   private List<Room> getVisibleRooms(Player player, int torchDistance) {
      Room tempRoom;
      int i;
      increaseToLightLevel(player.getX(), player.getY(), torchDistance, torchDistance);
      for (i = 1; i <= torchDistance; i++) {
         //NORTH
         increaseToLightLevel(player.getX(), player.getY() - i, torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(player.getX(), player.getY() - i));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(player.getX() + 1, player.getY() - i), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(player.getX() - 1, player.getY() - i), torchDistance - i, torchDistance);
      }
      for (i = 1; i <= torchDistance; i++) {            
         //EAST
         increaseToLightLevel(player.getX() + i, player.getY(), torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(player.getX() + i, player.getY()));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(player.getX() + i, player.getY() + 1), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(player.getX() + i, player.getY() - 1), torchDistance - i, torchDistance);
      }
      for (i = 1; i <= torchDistance; i++) {
         //SOUTH
         increaseToLightLevel(player.getX(), player.getY() + i, torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(player.getX(), player.getY() + i));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(player.getX() + 1, player.getY() + i), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(player.getX() - 1, player.getY() + i), torchDistance - i, torchDistance);
      }
      for (i = 1; i <= torchDistance; i++) {
         //WEST
         increaseToLightLevel(player.getX() - i, player.getY(), torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(player.getX() - i, player.getY()));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(player.getX() - i, player.getY() + 1), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(player.getX() - i, player.getY() - 1), torchDistance - i, torchDistance);
      }
      return lightRooms;
   }
   
   public Room getRoom(Point loc) {
      return getRoom(loc.x, loc.y);
   }

   private Room getRoom(Direction dir, Room room) {
      if (dir == Direction.NORTH) {
         return getRoom(room.getX(), room.getY() - 1);
      } else if (dir == Direction.EAST) {
         return getRoom(room.getX() + 1, room.getY());
      } else if (dir == Direction.SOUTH) {
         return getRoom(room.getX(), room.getY() + 1);
      } else {
         return getRoom(room.getX() - 1, room.getY());
      }
   }
   
   /**
    * Increases the light level of the room to int level
    */
   private void increaseToLightLevel(int x, int y, int lightLevel, int torchDistance) {
      Room room = getRoom(x, y);
      if (room == null) return;
      room.increaseToLight((float)lightLevel / torchDistance);
      if (lightLevel > 0 && !lightRooms.contains(room)) lightRooms.add(room);
      return;
   }
   
   /**
    * Increases the light level of the room to int level
    */
   private void increaseToLightLevel(Room room, int lightLevel, int torchDistance) {
      if (room == null) return;
      room.increaseToLight((float)lightLevel / torchDistance);
      if (lightLevel > 0 && !lightRooms.contains(room)) lightRooms.add(room);
   }
   
   private void increaseShadowRoomLight(Room room, int lightLevel, int torchDistance) {
      if (lightLevel <= 0) return;
      if (room.getTile() == Tiles.WALL) {
         increaseToLightLevel(room, lightLevel, torchDistance);
      } else {
         increaseToLightLevel(room, 1, torchDistance);
      }
   }
   
   public Point getPlayerStartLoc() {
      Collections.shuffle(spawns);
      for (Point p : spawns) {
         boolean tileFull = false;
         for (Player player : game.getPlayers()) {
            if (player.getLoc().equals(p)) {
               tileFull = true;
            }
         }
         if (!tileFull) return p;
      }
      return spawns.get(0);
   }
   
   public Point getExit() {
      return exit;
   }
   
   public List<Room> getAllRooms() {
      lightRooms.clear();
      for (int x = 0; x < getWidth(); x++) {
         for (int y = 0; y < getHeight(); y++) {
            Room room = map[x][y];
            room.setLight(1);
            lightRooms.add(room);
         }
      }
      return lightRooms;
   }
   
   public List<Lightable> getLights() {
      List<Lightable> lightables = new ArrayList<Lightable>();
      Lightable l = new Lightable();
      l.setLoc(getExit());
      lightables.add(l);
      return lightables;
   }

   public Room[][] getMap() {
      return map;
   }
   
}
